const crypto = require('crypto');
const url = require('url');

/**
 * Simple WebSocket implementation using Node.js built-in modules
 */
class SimpleWebSocket {
    constructor(socket, key) {
        this.socket = socket;
        this.isConnected = true;
        this.readyState = 1; // OPEN
        
        // Complete WebSocket handshake
        const acceptKey = this.generateAcceptKey(key);
        const responseHeaders = [
            'HTTP/1.1 101 Switching Protocols',
            'Upgrade: websocket',
            'Connection: Upgrade',
            `Sec-WebSocket-Accept: ${acceptKey}`,
            '', ''
        ].join('\r\n');
        
        socket.write(responseHeaders);
        
        // Setup socket handlers
        socket.on('data', (buffer) => {
            if (this.isConnected) {
                this.handleFrame(buffer);
            }
        });
        
        socket.on('close', () => {
            this.isConnected = false;
            this.readyState = 3; // CLOSED
            if (this.onclose) this.onclose();
        });
        
        socket.on('error', (error) => {
            if (this.onerror) this.onerror(error);
        });
    }
    
    generateAcceptKey(key) {
        return crypto
            .createHash('sha1')
            .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
            .digest('base64');
    }
    
    handleFrame(buffer) {
        if (buffer.length < 2) return;
        
        const firstByte = buffer[0];
        const secondByte = buffer[1];
        
        const opcode = firstByte & 0x0F;
        const masked = (secondByte & 0x80) === 0x80;
        let payloadLength = secondByte & 0x7F;
        
        let offset = 2;
        
        if (payloadLength === 126) {
            payloadLength = buffer.readUInt16BE(offset);
            offset += 2;
        } else if (payloadLength === 127) {
            payloadLength = buffer.readBigUInt64BE(offset);
            offset += 8;
        }
        
        let maskKey = null;
        if (masked) {
            maskKey = buffer.slice(offset, offset + 4);
            offset += 4;
        }
        
        const payload = buffer.slice(offset, offset + Number(payloadLength));
        
        if (masked && maskKey) {
            for (let i = 0; i < payload.length; i++) {
                payload[i] ^= maskKey[i % 4];
            }
        }
        
        if (opcode === 0x1) { // Text frame
            const message = payload.toString('utf8');
            if (this.onmessage) {
                this.onmessage({ data: message });
            }
        } else if (opcode === 0x8) { // Close frame
            this.close();
        } else if (opcode === 0x9) { // Ping frame
            this.pong(payload);
        }
    }
    
    send(data) {
        if (!this.isConnected || this.readyState !== 1) {
            return false;
        }
        
        const payload = Buffer.from(data, 'utf8');
        const frame = this.createFrame(0x1, payload);
        
        try {
            this.socket.write(frame);
            return true;
        } catch (error) {
            return false;
        }
    }
    
    ping(data = Buffer.alloc(0)) {
        if (this.isConnected) {
            const frame = this.createFrame(0x9, data);
            this.socket.write(frame);
        }
    }
    
    pong(data = Buffer.alloc(0)) {
        if (this.isConnected) {
            const frame = this.createFrame(0xA, data);
            this.socket.write(frame);
        }
    }
    
    createFrame(opcode, payload) {
        let frame;
        const payloadLength = payload.length;
        
        if (payloadLength < 126) {
            frame = Buffer.allocUnsafe(2);
            frame[0] = 0x80 | opcode;
            frame[1] = payloadLength;
        } else if (payloadLength < 65536) {
            frame = Buffer.allocUnsafe(4);
            frame[0] = 0x80 | opcode;
            frame[1] = 126;
            frame.writeUInt16BE(payloadLength, 2);
        } else {
            frame = Buffer.allocUnsafe(10);
            frame[0] = 0x80 | opcode;
            frame[1] = 127;
            frame.writeBigUInt64BE(BigInt(payloadLength), 2);
        }
        
        return Buffer.concat([frame, payload]);
    }
    
    close() {
        if (this.isConnected) {
            this.isConnected = false;
            this.readyState = 3;
            const frame = this.createFrame(0x8, Buffer.alloc(0));
            this.socket.write(frame);
            this.socket.end();
        }
    }
    
    terminate() {
        this.isConnected = false;
        this.readyState = 3;
        this.socket.destroy();
    }
}

/**
 * WebSocketServer handles real-time communication for the chat application
 */
class WebSocketServer {
    constructor(httpServer, roomManager) {
        this.roomManager = roomManager;
        this.clients = new Set();
        this.httpServer = httpServer;
        
        this.setupEventHandlers();
        console.log('WebSocket server initialized');
    }

    /**
     * Setup WebSocket event handlers
     */
    setupEventHandlers() {
        this.httpServer.on('upgrade', (request, socket, head) => {
            this.handleUpgrade(request, socket, head);
        });
    }
    
    /**
     * Handle HTTP upgrade to WebSocket
     */
    handleUpgrade(request, socket, head) {
        const { pathname } = url.parse(request.url);
        
        if (pathname !== '/ws') {
            socket.end('HTTP/1.1 404 Not Found\r\n\r\n');
            return;
        }
        
        const key = request.headers['sec-websocket-key'];
        if (!key) {
            socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
            return;
        }
        
        const ws = new SimpleWebSocket(socket, key);
        this.clients.add(ws);
        
        this.handleConnection(ws, request);
    }

    /**
     * Handle new WebSocket connection
     */
    handleConnection(ws, request) {
        const clientIp = request.socket.remoteAddress;
        console.log(`New WebSocket connection from ${clientIp}`);

        // Parse query parameters from URL
        const query = url.parse(request.url, true).query;
        
        ws.isAlive = true;
        ws.userId = null;
        ws.roomId = null;

        // Setup connection event handlers
        ws.onmessage = (event) => this.handleMessage(ws, event.data);
        ws.onclose = () => this.handleDisconnection(ws);
        ws.onerror = (error) => this.handleError(ws, error);
        
        // Setup pong handler
        ws.isAlive = true;
        const originalPong = ws.pong;
        ws.pong = (data) => {
            this.handlePong(ws);
            return originalPong.call(ws, data);
        };

        // Send welcome message
        this.sendMessage(ws, {
            type: 'connected',
            data: {
                message: 'Connected to chat server',
                timestamp: new Date()
            }
        });
    }

    /**
     * Handle incoming WebSocket message
     */
    handleMessage(ws, data) {
        try {
            const message = JSON.parse(data.toString());
            
            switch (message.type) {
                case 'join_room':
                    this.handleJoinRoom(ws, message.data);
                    break;
                    
                case 'leave_room':
                    this.handleLeaveRoom(ws);
                    break;
                    
                case 'chat_message':
                    this.handleChatMessage(ws, message.data);
                    break;
                    
                case 'ping':
                    this.handlePing(ws);
                    break;
                    
                case 'get_room_info':
                    this.handleGetRoomInfo(ws);
                    break;
                    
                default:
                    this.sendError(ws, `Unknown message type: ${message.type}`);
            }
        } catch (error) {
            console.error('Error handling WebSocket message:', error);
            this.sendError(ws, 'Invalid message format');
        }
    }

    /**
     * Handle user joining a room
     */
    handleJoinRoom(ws, data) {
        try {
            const { roomId, nickname, password, userId } = data;
            
            // Check if this is a reconnection with existing userId
            if (userId) {
                const existingUser = this.roomManager.users.get(userId);
                if (existingUser) {
                    // This is a reconnection or WebSocket connection for existing user
                    const userRoom = this.roomManager.getUserRoom(userId);
                    
                    if (userRoom && userRoom.id === roomId) {
                        // User already exists in the target room, just connect WebSocket
                        existingUser.websocket = ws;
                        existingUser.isOnline = true;
                        existingUser.updateActivity();
                        
                        // Update WebSocket mappings
                        this.roomManager.websockets.set(ws, userId);
                        
                        ws.userId = userId;
                        ws.roomId = roomId;
                        
                        this.sendMessage(ws, {
                            type: 'join_success',
                            data: {
                                user: existingUser.toJSON(),
                                room: userRoom.getDetailedInfo(),
                                reconnected: true
                            }
                        });
                        
                        this.broadcastRoomUpdate(roomId);
                        return;
                    }
                }
                
                // Try standard reconnection logic
                const reconnectedUser = this.roomManager.reconnectUser(userId, ws);
                if (reconnectedUser) {
                    ws.userId = userId;
                    ws.roomId = this.roomManager.userToRoom.get(userId);
                    
                    this.sendMessage(ws, {
                        type: 'join_success',
                        data: {
                            user: reconnectedUser.toJSON(),
                            room: this.roomManager.getUserRoom(userId).getDetailedInfo(),
                            reconnected: true
                        }
                    });
                    
                    this.broadcastRoomUpdate(ws.roomId);
                    return;
                }
            }

            // New user joining
            const result = this.roomManager.joinRoom(roomId, nickname, password, ws);
            
            ws.userId = result.user.id;
            ws.roomId = roomId;

            // Send success response to the joining user
            this.sendMessage(ws, {
                type: 'join_success',
                data: {
                    user: result.user.toJSON(),
                    room: result.room.getDetailedInfo()
                }
            });

            // Broadcast to all users in the room that someone joined
            this.broadcastToRoom(roomId, {
                type: 'user_joined',
                data: {
                    user: result.user.toJSON(),
                    message: `${result.user.nickname} joined the room`
                }
            }, result.user.id);

            // Broadcast updated room info
            this.broadcastRoomUpdate(roomId);

        } catch (error) {
            console.error('Error joining room:', error);
            this.sendError(ws, error.message);
        }
    }

    /**
     * Handle user leaving a room
     */
    handleLeaveRoom(ws) {
        if (!ws.userId) {
            return;
        }

        try {
            const result = this.roomManager.leaveRoom(ws.userId);
            
            if (result) {
                // Broadcast to remaining users
                this.broadcastToRoom(result.room.id, {
                    type: 'user_left',
                    data: {
                        user: result.user.toJSON(),
                        message: `${result.user.nickname} left the room`
                    }
                });

                this.broadcastRoomUpdate(result.room.id);
            }

            ws.userId = null;
            ws.roomId = null;

        } catch (error) {
            console.error('Error leaving room:', error);
        }
    }

    /**
     * Handle chat message
     */
    handleChatMessage(ws, data) {
        if (!ws.userId || !ws.roomId) {
            this.sendError(ws, 'Not in a room');
            return;
        }

        try {
            const { content } = data;
            const result = this.roomManager.addMessage(ws.userId, content);

            // Broadcast message to all users in the room
            this.broadcastToRoom(ws.roomId, {
                type: 'new_message',
                data: {
                    message: result.message.toJSON(),
                    user: this.roomManager.users.get(ws.userId).toJSON()
                }
            });

        } catch (error) {
            console.error('Error handling chat message:', error);
            this.sendError(ws, error.message);
        }
    }

    /**
     * Handle ping for connection health check
     */
    handlePing(ws) {
        this.sendMessage(ws, {
            type: 'pong',
            data: { timestamp: new Date() }
        });
    }

    /**
     * Handle pong response
     */
    handlePong(ws) {
        ws.isAlive = true;
    }

    /**
     * Handle getting room information
     */
    handleGetRoomInfo(ws) {
        if (!ws.roomId) {
            this.sendError(ws, 'Not in a room');
            return;
        }

        const room = this.roomManager.getRoom(ws.roomId);
        if (room) {
            this.sendMessage(ws, {
                type: 'room_info',
                data: room.getDetailedInfo()
            });
        } else {
            this.sendError(ws, 'Room not found');
        }
    }

    /**
     * Handle WebSocket disconnection
     */
    handleDisconnection(ws) {
        console.log(`WebSocket disconnected for user ${ws.userId}`);
        
        // Remove from clients set
        this.clients.delete(ws);
        
        if (ws.userId) {
            this.roomManager.handleDisconnect(ws);
            
            // Broadcast user offline status
            if (ws.roomId) {
                this.broadcastRoomUpdate(ws.roomId);
            }
        }
    }

    /**
     * Handle WebSocket errors
     */
    handleError(ws, error) {
        console.error('WebSocket error:', error);
        
        if (ws.userId) {
            this.roomManager.handleDisconnect(ws);
        }
    }

    /**
     * Send message to a specific WebSocket connection
     */
    sendMessage(ws, message) {
        if (ws.readyState === 1) { // OPEN
            try {
                ws.send(JSON.stringify(message));
                return true;
            } catch (error) {
                console.error('Error sending WebSocket message:', error);
                return false;
            }
        }
        return false;
    }

    /**
     * Send error message to WebSocket connection
     */
    sendError(ws, errorMessage) {
        this.sendMessage(ws, {
            type: 'error',
            data: {
                message: errorMessage,
                timestamp: new Date()
            }
        });
    }

    /**
     * Broadcast message to all users in a room
     */
    broadcastToRoom(roomId, message, excludeUserId = null) {
        const room = this.roomManager.getRoom(roomId);
        if (!room) {
            return;
        }

        const users = room.getOnlineUsers();
        let sentCount = 0;

        for (const user of users) {
            if (excludeUserId && user.id === excludeUserId) {
                continue;
            }

            if (user.websocket && this.sendMessage(user.websocket, message)) {
                sentCount++;
            }
        }

        return sentCount;
    }

    /**
     * Broadcast room update (user list, etc.) to all users in room
     */
    broadcastRoomUpdate(roomId) {
        const room = this.roomManager.getRoom(roomId);
        if (!room) {
            return;
        }

        this.broadcastToRoom(roomId, {
            type: 'room_update',
            data: {
                users: room.getOnlineUsers().map(user => user.toJSON()),
                memberCount: room.getOnlineUsers().length
            }
        });
    }

    /**
     * Periodic health check for WebSocket connections
     */
    startHealthCheck() {
        setInterval(() => {
            this.clients.forEach((ws) => {
                if (!ws.isAlive) {
                    console.log('Terminating unresponsive WebSocket connection');
                    this.clients.delete(ws);
                    return ws.terminate();
                }

                ws.isAlive = false;
                ws.ping();
            });
        }, 30000); // Check every 30 seconds
    }

    /**
     * Get WebSocket server statistics
     */
    getStats() {
        return {
            totalConnections: this.clients.size,
            activeConnections: Array.from(this.clients).filter(ws => ws.readyState === 1).length
        };
    }

    /**
     * Shutdown WebSocket server gracefully
     */
    shutdown() {
        console.log('Shutting down WebSocket server...');
        
        // Notify all clients about shutdown
        this.clients.forEach((ws) => {
            this.sendMessage(ws, {
                type: 'server_shutdown',
                data: {
                    message: 'Server is shutting down',
                    timestamp: new Date()
                }
            });
            ws.close();
        });
        
        this.clients.clear();
        console.log('WebSocket server closed');
    }
}

module.exports = WebSocketServer;